package com.guanjialhx.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import com.google.gson.Gson;
import com.guanjialhx.config.WeiXinConfig;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.model.TransactionPayer;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 微信支付成功和微信退款成功的回调接口
 */
@RestController
@RequestMapping("/api")
@Transactional(rollbackFor = Exception.class)
public class WeChatPayInfoController {
    //日志打印
    private final Logger log = LoggerFactory.getLogger(WeChatPayInfoController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 访问启效云的参数
     */
    private static final String appKey = "d3aa45bbd1e94d70b5fe59b1fa37da21";
    private static final String appSecret = "883fbc209be344e899544af7c7caa0fc";
    private static final String qxUrl = "https://changhu.qixiaocloud.com";

    /**
     * 支付成功之后处理订单回调事件
     *
     * @param info
     * @return
     */
    @PostMapping("/pay/notifi")
    public String savePayOrder(
            @RequestHeader(value = "Wechatpay-Signature") String sign,
            @RequestHeader(value = "Wechatpay-Serial") String serial,
            @RequestHeader(value = "Wechatpay-Nonce") String nonce,
            @RequestHeader(value = "Wechatpay-Timestamp") String timestamp,
            @RequestHeader(value = "Wechatpay-Signature-Type") String signType,
            @RequestBody String info
    ) {
        /**
         * 注意:
         * 1.请求头必须按照代码中的定义格式(如果接收不到可以试试@RequestHeader(value = "Wechatpay-Signature",required = false) String sign)在里面加一个required = false
         * 2.接收body请求体必须是String类型,不能使用json,如果使用json会导致解析不正确
         * 3.返回值类型可以参考文档,我这边定义的String,看文档需求是成功返回"SUCCESS",失败为"FAIL"
         * 3.其他问题可以参考文档代码:https://github.com/wechatpay-apiv3/wechatpay-java/tree/main
         * 4.解析出来的参数问题可以参考这个文档:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/payment-notice.html
         */
        try {
            log.debug(
                    "获取到回调事件,请求头信息 sign:{}, serial:{}, nonce:{}, timestamp:{}, signType:{}, info:{}",
                    sign,
                    serial,
                    nonce,
                    timestamp,
                    signType,
                    info
            );

            // 构造 RequestParam
            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                    .serialNumber(serial)
                    .nonce(nonce)
                    .signature(sign)
                    .timestamp(timestamp)
                    .body(info)
                    .build();

            // 如果已经初始化了 RSAAutoCertificateConfig，可直接使用
            NotificationConfig config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(WeiXinConfig.MERCHANT_ID)
                    .privateKeyFromPath(WeiXinConfig.MERCHANT_PRIVATE_KEY_PATH)
                    .merchantSerialNumber(WeiXinConfig.MERCHANT_SERIAL_NUMBER)
                    .apiV3Key(WeiXinConfig.API_V3_KEY)
                    .build();

            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(config);

            // 以支付通知回调为例，验签、解密并转换成 Transaction
            Transaction payInfo = null;
            try {
                //回调所有的支付信息
                payInfo = parser.parse(requestParam, Transaction.class);
            } catch (Exception e) {
                log.error(
                        "签名验证失败,获取到回调事件,请求头信息 sign:{}, serial:{}, nonce:{}, timestamp:{}, signType:{}, info:{}",
                        sign,
                        serial,
                        nonce,
                        timestamp,
                        signType,
                        info
                );
                log.error(e.getMessage());
                return "FAIL";
            }
            /**
             * 获取支付信息
             */
            //获取支付人员的信息
            TransactionPayer payerInfo = payInfo.getPayer();
            //获取自定义信息
            JSONObject meetInfo = JSON.parseObject(payInfo.getAttach());
            log.info("所有参数在下面：");
            System.out.println();
            //支付具体信息自行获取即可
            System.out.println(payInfo.toString());
            String outTradeNo = payInfo.getOutTradeNo(); //商户订单号
            String transactionId = payInfo.getTransactionId(); //支付单号
//        String successTime = payInfo.getSuccessTime().substring(0, 10); //支付时间
            String successTime = payInfo.getSuccessTime(); //支付时间
            successTime = DataType(successTime);
            String openid = payerInfo.getOpenid();//支付人微信的openid
            /**
             * 获取报名信息
             * 报名人员的信息 使用Gson库将String格式的Map转换为Map对象
             */
            String attach = payInfo.getAttach();
            Gson gson = new Gson();
            Type type = new com.google.gson.reflect.TypeToken<Map<String, String>>() {
            }.getType();
            Map<String, String> map = gson.fromJson(attach, type);
            String lsname = map.get("lsname"); //创客老师姓名  c端报名支付的时候是没有这个值的
            String bmid = map.get("bmid"); //创客老师的渠道id  c端报名支付的时候是没有这个值的
            String bmName = map.get("bmName"); //报名学员姓名
            String bmUID = map.get("bmUID"); //报名学员身份证
            String bmTel = map.get("bmTel"); //报名学员电话
            String bmtype = (map.get("bmtype").equals("9") ? "1990" : "1790"); //报名类型 1990=9或者1770=7
            String birthDate = getBirthDateFromIDCard(bmUID); //出生日期
            String gender = getGenderFromIDCard(bmUID); //性别
            /**
             * 去启效云查询
             */
            String CKopenid = "";
            if (!bmid.equals("")){
                String qxToken2 = this.token();
                Map<String, String> headMap2 = new HashMap<>();
                headMap2.put("Authorization", qxToken2);
                Map param2 = new HashMap();
                String url2 = "https://changhu.qixiaocloud.com/open/qx-apaas-lowcode/gj/SelectTeacherById";//查询创客老师openid
                log.info("创客id："+bmid);
                param2.put("ckID", bmid);
                String qxAdd2 = WeChatPayInfoController.postJsonParam(url2, param2, headMap2);
                net.sf.json.JSONObject qxAddJJson2 = net.sf.json.JSONObject.fromObject(qxAdd2);
                net.sf.json.JSONObject data2 = net.sf.json.JSONObject.fromObject(qxAddJJson2.getString("data"));
                CKopenid = data2.getString("openid");
            }
            /**
             * 新增数据到启效云管家系统的报名表单
             */
            //判断是否在有效时间里面
            String qxToken = this.token();
            /** 新增报名数据和培训数据  **/
            Map<String, String> headMap = new HashMap<>();
            Map param = new HashMap();
            String url = "https://changhu.qixiaocloud.com/open/qx-apaas-lowcode/gj/AddApply";//新增报名数据
            headMap.put("Authorization", qxToken);
            param = new HashMap();
            param.put("txtlsname", lsname); //创客老师姓名  c端报名支付的时候是没有这个值的
            param.put("txtbmid", bmid);     //创客老师姓名  c端报名支付的时候是没有这个值的
            param.put("txtbmName", bmName);
            param.put("txtbmUID", bmUID);
            param.put("txtbmTel", bmTel);
            bmtype = (bmtype.equals("1790") ? "1" : "2");
            param.put("txtbmtype", bmtype);
            param.put("txtbirthDate", birthDate);
            param.put("txtgender", gender);
            param.put("txtoutTradeNo", outTradeNo);
            param.put("txttransactionId", transactionId);
            param.put("txtsuccessTime", successTime);
            param.put("txtopenid", openid);
            if (!bmid.equals("")){
                param.put("CKopenid", CKopenid);
            }
            param.put("txtamount", bmtype.equals("1") ? 1790 : 1990); //总金额
            param.put("RebateAmount", bmtype.equals("1") ? 700 : 800); //返费金额
            String qxAdd = WeChatPayInfoController.postJsonParam(url, param, headMap);
            log.info("新增报名培训数据 = " + qxAdd);
            /** 新增培训数据  **/
//            Map<String, String> headMap2 = new HashMap<>();
//            Map param2 = new HashMap();
//            String url2 = "https://changhu.qixiaocloud.com/open/qx-apaas-lowcode/gj/insertpx";//新增培训数据
//            qxToken = this.token();
//            headMap2.put("Authorization", qxToken);
//            param2 = new HashMap();
//            param2.put("txtnamepx", bmName);
//            param2.put("txtphonepx", bmTel);
//            param2.put("txtSexpx", gender);
//            param2.put("txtsfzpx", bmUID);
//            param2.put("txtTypepx", bmtype);
//            String qxAdd2 = WeChatPayInfoController.postJsonParam(url2, param2, headMap2);
//            log.info("新增培训数据 = " + qxAdd2);
            log.info("创客老师姓名：" + lsname + " 创客老师的渠道id：" + bmid +
                    " 报名学员姓名:" + bmName + " 报名学员身份证:" + bmUID +
                    " 报名学员电话:" + bmTel + " 报名类型:" + bmtype +
                    " 出生日期:" + birthDate + " 性别:" + gender + " 商户订单号:" + outTradeNo +
                    " 支付单号:" + transactionId + " 支付时间:" + successTime + " 支付人微信的openid:" + openid + " 总金额:" + (bmtype.equals("1") ? 1790 : 1990));

            return "SUCCESS";
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        /**
         * 以上代码只是拿到回调的签名,解析出来回调的支付信息,具体业务根据自己的实际情况自行编写
         */
    }


    /**
     * 处理退款订单回调事件
     *
     * @param info
     * @return
     */
    @PostMapping("/refund/notifi")
    public String saveReFundOrder(
            @RequestHeader(value = "Wechatpay-Signature", required = false) String sign,
            @RequestHeader(value = "Wechatpay-Serial", required = false) String serial,
            @RequestHeader(value = "Wechatpay-Nonce", required = false) String nonce,
            @RequestHeader(value = "Wechatpay-Timestamp", required = false) String timestamp,
            @RequestHeader(value = "Wechatpay-Signature-Type", required = false) String signType,
            @RequestBody String info
    ) {
        /**
         * 注意:
         * 1.请求头必须按照代码中的定义格式
         * 2.接收body请求体必须是String类型,不能使用json,如果使用json会导致解析不正确
         * 3.返回值类型可以参考文档,我这边定义的String,看文档需求是成功返回"SUCCESS",失败为"FAIL"
         * 3.其他问题可以参考文档代码:https://github.com/wechatpay-apiv3/wechatpay-java/tree/main
         * 4.解析出来的参数问题可以参考这个文档:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/payment-notice.html
         */

        log.debug(
                "获取到回调事件,请求头信息 sign:{}, serial:{}, nonce:{}, timestamp:{}, signType:{}, info:{}",
                sign,
                serial,
                nonce,
                timestamp,
                signType,
                info
        );

        // 构造 RequestParam
        com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce)
                .signature(sign)
                .timestamp(timestamp)
                .body(info)
                .build();

        // 如果已经初始化了 RSAAutoCertificateConfig，可直接使用
        NotificationConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(WeiXinConfig.MERCHANT_ID)
                .privateKeyFromPath(WeiXinConfig.MERCHANT_PRIVATE_KEY_PATH)
                .merchantSerialNumber(WeiXinConfig.MERCHANT_SERIAL_NUMBER)
                .apiV3Key(WeiXinConfig.API_V3_KEY)
                .build();

        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser(config);
        //获取退款回调的信息
        RefundNotification refundInfo = parser.parse(requestParam, RefundNotification.class);
        /**
         * 退款成功，信息都在refundInfo这个里面，现在需要去修改启效云里面退款学员的信息
         */

        System.out.println(refundInfo);
        String refundId = refundInfo.getRefundId();//微信支付退款号
        String outRefundNo = refundInfo.getOutRefundNo();//商户退款单号
        String successTime = refundInfo.getSuccessTime();//退款时间
        successTime = DataType(successTime);
        /**
         * userReceivedAccount【退款入账账户】 取当前退款单的退款入账方，有以下几种情况：
         * 1）退回银行卡：{银行名称}{卡类型}{卡尾号}
         * 2）退回支付用户零钱:支付用户零钱
         * 3）退还商户:商户基本账户商户结算银行账户
         * 4）退回支付用户零钱通:支付用户零钱通
         */
        String userReceivedAccount = refundInfo.getUserReceivedAccount();//退款入账账户
        String outTradeNo = refundInfo.getOutTradeNo();//原商户订单号
        String transactionId = refundInfo.getTransactionId(); //原微信支付订单号
        try {
            //判断是否在有效时间里面
            String qxToken = stringRedisTemplate.opsForValue().get("token");
            if (qxToken == null) {
                qxToken = this.token();
            }
            Map<String, String> headMap = new HashMap<>();
            Map param = new HashMap();
            String url = "https://changhu.qixiaocloud.com/open/qx-apaas-lowcode/gj/UpdateRefund";
            headMap.put("Authorization", qxToken);
            param = new HashMap();
            param.put("refundId", refundId);
            param.put("outRefundNo", outRefundNo);
            param.put("successTime", successTime);
            param.put("ReceivedAccount", userReceivedAccount);
            param.put("outTradeNo", outTradeNo);
            param.put("transactionId", transactionId);
            String qxAdd = WeChatPayInfoController.postJsonParam(url, param, headMap);
            System.out.println("qxAdd = " + qxAdd);
            log.info("微信支付退款号：" + refundId + " 商户退款单号：" + outRefundNo +
                    " 退款时间:" + successTime + " 退款入账账户:" + userReceivedAccount +
                    " 原商户订单号:" + outTradeNo + " 原微信支付订单号:" + transactionId);
            return "SUCCESS";
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }


        /**
         * 以上代码用于获取退款回调的信息,写法与支付回调很像,具体业务根据自己实际情况修改
         * 回调的具体参数参考文档:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/refund-result-notice.html
         */
    }


    /**
     * 下面是工具方法--------------------------------------------------------------------------------------------------------------
     */

    public String DataType(String data) {
        // 创建 SimpleDateFormat 对象来解析输入日期字符串
        SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        inputFormat.setTimeZone(TimeZone.getTimeZone("GMT+8")); // 设置时区以匹配输入字符串的时区
        String outputDateFormat = "yyyy-MM-dd HH:mm:ss";
        // 创建 SimpleDateFormat 对象来格式化输出日期字符串
        SimpleDateFormat outputFormat = new SimpleDateFormat(outputDateFormat);
        outputFormat.setTimeZone(TimeZone.getTimeZone("GMT+8")); // 通常输出格式不需要时区，但这里保持一致
        try {
            // 解析输入日期字符串
            java.util.Date date = inputFormat.parse(data);
            // 格式化日期为所需的输出格式
            return outputFormat.format(date);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 生成访问启效云的token
     */
    public String token() {
        try {
            String url = qxUrl + "/open/oauth/token";
            Map<String, String> headMap = new HashMap<>();
            Map param = new HashMap();
            param.put("appKey", appKey);
            param.put("appSecret", appSecret);
            String qxLogin = null;
            qxLogin = WeChatPayInfoController.postJsonParam(url, param, headMap);
            net.sf.json.JSONObject qxLoginJson = net.sf.json.JSONObject.fromObject(qxLogin);
            String success = qxLoginJson.getString("success");
            if ("true".equals(success)) {
                net.sf.json.JSONObject qxData = net.sf.json.JSONObject.fromObject(qxLoginJson.getString("data"));
                String qxToken = qxData.getString("access_token");
                //将token存到redis设置全局有效时间 10分钟
                System.out.println("stringRedisTemplate = " + stringRedisTemplate);
                stringRedisTemplate.opsForValue().set("token", qxToken, 10, TimeUnit.MINUTES);
                return qxToken;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将map数据转成json数据
     */
    public static String postJsonParam(String url, Map<String, String> param, Map<String, String> headMap) throws Exception {
        String jsonparam = mapToJSON(param);
        return doPost(url, jsonparam, headMap);
    }

    /**
     * map 转 json
     *
     * @param param
     * @return
     */
    public static String mapToJSON(Map param) {
        String json = JSON.toJSONString(param);
        return json;
    }

    /**
     * post请求（用于请求json格式的参数）
     *
     * @param url
     * @param params
     * @param headMap
     * @return
     */
    public static String doPost(String url, String params, Map<String, String> headMap) throws Exception {

        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);// 创建httpPost
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("Content-Type", "application/json");
        //如果headMap不为空，则需要添加接口鉴权字段
        if (headMap != null) {
            Iterator it = headMap.keySet().iterator();
            while (it.hasNext()) {
                String key = it.next().toString();
                httpPost.setHeader(key, headMap.get(key));
            }
        }
        String charSet = "UTF-8";
        StringEntity entity = new StringEntity(params, charSet);
        httpPost.setEntity(entity);
        CloseableHttpResponse response = null;
        try {
            response = httpclient.execute(httpPost);
            StatusLine status = response.getStatusLine();
            int state = status.getStatusCode();
            if (state == HttpStatus.SC_OK) {
                HttpEntity responseEntity = response.getEntity();
                String jsonString = EntityUtils.toString(responseEntity, "UTF-8");
                return jsonString;
            } else {
                System.out.println("!!!!!!!!responseEntity:" + EntityUtils.toString(response.getEntity(), "UTF-8"));
                return "请求失败，服务器状态码：" + state;
            }
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 根据身份证计算出生年月
     */
    public static String getBirthDateFromIDCard(String idCardNumber) {
        // 身份证号码中出生日期的位置
        String birthDatePart = idCardNumber.substring(6, 14);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        try {
            Date birthDate = sdf.parse(birthDatePart);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(birthDate);
            int year = calendar.get(Calendar.YEAR);
            int month = calendar.get(Calendar.MONTH) + 1;
            int day = calendar.get(Calendar.DAY_OF_MONTH);
            return String.format("%d-%d-%d", year, month, day);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据身份证计算性别
     */
    public static String getGenderFromIDCard(String idCardNumber) {
        // 身份证号码中性别码的位置
        int genderCode = Integer.parseInt(idCardNumber.substring(16, 17));
        if (genderCode % 2 == 0) {
            return "2";
        } else {
            return "1";
        }
    }


}
